<?php

namespace {
    include './FourInALinePlayer.php';

    class DankerBotPlayer extends FourInALinePlayer {

        private $board;
        private $init = false;
        private $stateMachine;

        private function init() {
            $moveChecker = new \DankerBot\MoveChecker();
            $this->board = new \DankerBot\Board(array(), $this, $moveChecker);
            $this->stateMachine = new \DankerBot\StateMachine($this, $this->board, $moveChecker);
        }

        public function getHeight() {
            return $this->height;
        }

        public function getWidth() {
            return $this->width;
        }

        public function getMustMove() {
            $move = $this->getBoard()->getMoveChecker()->isOneMoveToWin();
            if ($move !== false) {
                return $move;
            }

            $move = $this->getBoard()->getMoveChecker()->isOneEnemyMoveToWin();
            if ($move !== false) {
                return $move;
            }

            return false;
        }

        public function getCalculateMove() {
            return $this->stateMachine->getMove();
        }

        public function getMoveByStrategy() {
            
        }

        public function getMove(array $grid) {
            if ($this->init == false) {
                $this->init = true;
                $this->init();
            }
            $this->board->setGrid($grid);

            $move = $this->getMustMove();
            if ($move !== false) {
                return $move;
            }

            $move = $this->getCalculateMove();
            if ($move !== false) {
                return $move;
            }

            //TODO remove
            $move = false;
            $counter = 0;

            while ($move == false && $counter++ < 100) {
                $move = mt_rand(0, $this->width - 1);
                if (!isset($grid[$move][$this->getBoard()->getHeight() - 1])) {
                    $temp = $grid;
                    $this->stateMachine->addMove($temp, $move);
                    $this->getBoard()->setGrid($temp);
                    if ($this->getBoard()->getMoveChecker()->isOneEnemyMoveToWin()) {
                        $move = false;
                        $this->getBoard()->setGrid($grid);
                        continue;
                    }
                    return $move;
                }
                $move = false;
            }
            return $move;
        }

        public function getName() {
            return "Danker's eliminator";
        }

        public function getBoard() {
            return $this->board;
        }

        public function getCurrentPlayerNumber() {
            return $this->currentPlayer;
        }

        public function getEnemeyPlayerNumber() {
            $current = $this->getCurrentPlayerNumber();
            $enemy = ($current == \FourInALinePlayer::PLAYER_ONE) ? \FourInALinePlayer::PLAYER_TWO
                        : \FourInALinePlayer::PLAYER_ONE;
            return $enemy;
        }

    }

}

namespace DankerBot {

    abstract class Strategy {

        public function analyse(Board $board) {
            
        }

        public function run(Strategy $strategy) {
            
        }

    }

    class Board {

        private $grid;
        private $player;
        private $moveChecker;
        private $width;
        private $height;
        static protected $_displayValues = array(\FourInALinePlayer::PLAYER_ONE => 'X',
            \FourInALinePlayer::PLAYER_TWO => 'O',
            \ConnectFour::DISC_EMPTY => '.');

        public function __construct(array $grid, \DankerBotPlayer $player,
                MoveChecker $checker) {
            $this->grid = $grid;
            $this->moveChecker = $checker;
            $this->player = $player;
            $this->moveChecker->setBoard($this);
            $this->width = $this->getPlayer()->getWidth();
            $this->height = $this->getPlayer()->getHeight();
        }

        public function getGrid() {
            return $this->grid;
        }

        public function setGrid(array $grid) {
            $this->grid = $grid;
        }

        public function getMoveChecker() {
            return $this->moveChecker;
        }

        public function getWidth() {
            return $this->width;
        }

        public function getHeight() {
            return $this->width;
        }

        /**
         *
         * @return DankerBotPlayer
         */
        public function getPlayer() {
            return $this->player;
        }

        public function getDisplayGrid() {
            $displayGrid = "";
            for ($i = ($this->height - 1); $i >= 0; $i--) {
                $displayGrid .= "\n    ";
                for ($j = 0; $j < $this->width; $j++) {
                    $char = $this->grid[$j][$i];
                    $displayGrid .= self::getDiscDisplayValue($char) . " ";
                }
            }
            $displayGrid.= "\n";
            return $displayGrid;
        }

        /**
         * Convert the internal disc value to a display value
         *
         * @param int $discValue
         * @return string the display value
         */
        protected static function getDiscDisplayValue($discValue) {
            $displayValue = self::$_displayValues[$discValue];

            if (!isset($displayValue)) {
                $displayValue = $discValue;
            }

            return $displayValue;
        }

    }

    class State {

        private $grid;
        /**
         *
         * @var Status
         */
        private $status;
        /**
         *
         * @var State
         */
        private $previous;
        private $column;
        private $player;
        private $childScore;

        public function __construct(array $grid, $previous, $column, $player) {
            $this->grid = $grid;
            $this->previous = $previous;
            $this->column = $column;
            $this->player = $player;
        }

        public function getPrevious() {
            return $this->previous;
        }

        public function getStatus() {
            return $this->status;
        }

        public function setStatus(Status $status) {
            if (isset($this->status)) {
                throw new \Exception("Cannot change state");
            }

            $this->status = $status;
            $this->updateChildScore(0);
        }

        protected function setGrid(array $grid) {
            $this->grid = $grid;
        }

        public function getGrid() {
            return $this->grid;
        }

        public function getColumn() {
            return $this->column;
        }

        protected function updateChildScore($myScore) {
            if ($this->getStatus() == null) {
                \debug_print_backtrace();
            }
            $this->childScore = $this->getStatus()->getValue() + $myScore;
            if (isset($this->previous)) {
                $this->previous->updateChildScore($this->childScore);
            }
        }

        /**
         * Returns player number that made move
         * @return Integer
         */
        public function getPlayer() {
            return $this->player;
        }

        protected function setChildScore($score) {
            $this->score = $score;
        }

        public function getChildScore() {
            return $this->score;
        }

    }

    class Status {

        /**
         *
         * @var Integer
         */
        private $status;

        const DRAW = 1;
        const LOSS = 0;
        const WIN = 2;

        public function __construct($status) {
            if (!\in_array($status, array(self::DRAW, self::LOSS, self::WIN))) {
                throw new \Exception("Wrong status");
            }
            $this->status = $status;
        }

        public function getValue() {
            return $this->status;
        }

    }

    class StateMachine {

        /**
         *
         * @var MoveChecker
         */
        private $moveChecker;
        /**
         * @var Player
         */
        private $player;
        private $board;
        private $startingGrid;
        private $counter;
        private $depth;

        public function __construct(\DankerBotPlayer $player, Board $board,
                MoveChecker $moveChecker) {

            if ($moveChecker->getBoard() !== $board) {
                throw new \Exception("Board should be the same");
            }

            $this->moveChecker = $moveChecker;
            $this->player = $player;
            $this->board = $board;

        }

        public function getMoveChecker() {
            return $this->moveChecker;
        }

        public function getCounter() {
            return $this->counter;
        }

        public function getDepth() {
            return $this->depth;
        }

        public function getBoard() {
            return $this->board;
        }

        /**
         *
         * @param array $grid
         * @return Array Available columns for insertion
         */
        public function getAvailableMoves(array $grid) {
            $rez = array();
            $height = $this->getBoard()->getHeight() - 1;
            $width = $this->getBoard()->getWidth();
            for ($i = 0; $i < $width; $i++) {
                if ($grid[$i][$height] == \ConnectFour::DISC_EMPTY) {
                    $rez[] = $i;
                }
            }

            return $rez;
        }

        /**
         * Assumes that move is valid because we got it from available moves
         * @param Array $grid
         * @param Integer $column
         * @return Bool Success value
         */
        public function addMove(&$grid, $column) {
            return $this->addPlayerMove($this->getPlayer()->getCurrentPlayerNumber(), $grid, $column);
        }

        public function addEnemyMove(&$grid, $column) {
            return $this->addPlayerMove($this->getPlayer()->getEnemeyPlayerNumber(), $grid, $column);
        }

        protected function addPlayerMove($player, &$grid, $column) {
            $col = &$grid[$column];
            for ($i = 0; $i < count($col); $i++) {
                if ($col[$i] == \ConnectFour::DISC_EMPTY) {
                    $col[$i] = $player;
                    //var_dump($grid);
                    return true;
                }
            }
            return false;
        }

        /**
         * Returns false if there is not good moves available and number if there is
         * @return Mixed
         */
        public function getMove() {
            $this->startingGrid = $this->getBoard()->getGrid();

            $action = true;
            $grid = $this->startingGrid;
            $counter = 0;
            $endMove = false;
            $previousState = null;
            $depth = 0;

            $this->counter = 0;


            $state = new State($grid, $previousState, $column, $this->getPlayer()->getCurrentPlayerNumber());
            $state->setStatus(new Status(Status::DRAW));

            $endMove = $this->getStateMove($state);

            $this->getBoard()->setGrid($this->startingGrid);
            echo "Counter: {$this->counter}\n";
            return $endMove;
        }

        protected function otherPlayer($player) {
            $enemy = ($player == \FourInALinePlayer::PLAYER_ONE) ? \FourInALinePlayer::PLAYER_TWO
                        : \FourInALinePlayer::PLAYER_ONE;
            return $enemy;
        }

        protected function getStateMove(State $state) {
            if ($this->counter++ > 20) {
                return false;
            }
            $grid = $state->getGrid();
            $moves = $this->getAvailableMoves($grid);
            $states = array();
            $player = $state->getPlayer();
            $player = $this->otherPlayer($player);

            foreach ($moves as $column) {
                $this->addMove($grid, $column);
                $nState = new State($grid, $state, $column, $player);
                $this->getBoard()->setGrid($state->getGrid());
                $moveToWin = $this->getMoveChecker()->isOneEnemyMoveToWin();

                if ($player == \FourInALinePlayer::PLAYER_ONE && $moveToWin === false) {
                    $nState->setStatus(new Status(Status::DRAW));
                    $states[] = $nState;
                    $this->getStateMove($nState);
                } else if ($player == \FourInALinePlayer::PLAYER_ONE && $moveToWin !== false) {
                    $nState->setStatus(new Status(Status::LOSS));
                } else if ($player == \FourInALinePlayer::PLAYER_TWO && $this->getMoveChecker()->isOneMoveToWin() === false) {
                    $nState->setStatus(new Status(Status::DRAW));
                    $states[] = $nState;
                    $this->getStateMove($nState);
                } else if ($player == \FourInALinePlayer::PLAYER_TWO && $this->getMoveChecker()->isOneMoveToWin() !== false) {
                    $nState->setStatus(new Status(Status::WIN));
                    $states[] = $nState;
                } else {
                    $nState->setStatus(new Status(Status::DRAW));
                    $states[] = $nState;
                }
            }

            $score = 0;
            $move = false;

            foreach ($states as $state) {
                if ($state->getChildScore() > $score) {
                    $score = $state->getChildScore();
                    $move = $state->getColumn();
                }
            }

            return $move;
        }

        public function getPlayer() {
            return $this->player;
        }

    }

    class MoveChecker {

        private $board;

        public function __construct() {

        }

        public function setBoard(Board $board) {
            $this->board = $board;
        }

        public function getBoard() {
            return $this->board;
        }

        public function isOneMoveToWin() {
            return $this->checkOneMove($this->getBoard()->getPlayer()->getCurrentPlayerNumber());
        }

        public function isOneEnemyMoveToWin() {
            $enemy = $this->getBoard()->getPlayer()->getEnemeyPlayerNumber();
            return $this->checkOneMove($enemy);
        }

        public function checkOneMove($player) {

            $width = $this->getBoard()->getPlayer()->getWidth();
            $height = $this->getBoard()->getPlayer()->getHeight();


            // $i - columns
            // $j - rows
            $grid = $this->getBoard()->getGrid();
            for ($i = 0; $i < $width; $i++) {
                // each row
                for ($j = 0; $j < $height; $j++) {

                    if ($i < $width - 3) {
                        $val = $this->horizontal($i, $j, $player);
                        if ($val !== false) return $val;
                    }
                    if ($j < $height - 3) {
                        $val = $this->vertical($i, $j, $player);
                        if ($val !== false) return $val;
                    }
                    if ($i < $width - 3 && $j < $height - 3) {
                        $val = $this->diagonal($i, $j, $player);
                        if ($val !== false) return $val;
                    }
                    if ($i >= 3 && $j < $height - 3) {
                        $val = $this->diagonal($i, $j, $player, -1);
                        if ($val !== false) return $val;
                    }
                }
            }
            return false;
        }

        protected function horizontal($column, $row, $player) {
            $grid = $this->getBoard()->getGrid();

            $width = $this->getBoard()->getPlayer()->getWidth();
//            echo __METHOD__ . "\n";
            $empty = 0;
            $col = 0;
            for ($i = 0; $i <= 3; $i++) {
                if ($grid[$column + $i][$row] === \ConnectFour::DISC_EMPTY) {
                    if (++$empty == 2) {
                        return false;
                    }
                    $col = $column + $i;
                } else if ($grid[$column + $i][$row] != $player) {
                    return false;
                }
            }
            return $col;
        }

        protected function vertical($column, $row, $player) {
            $grid = $this->getBoard()->getGrid();

            $height = $this->getBoard()->getPlayer()->getHeight();

            $count = 0;
//            echo __METHOD__ . "\n";

            if (($row + 3) >= $this->getBoard()->getHeight() ||
                    ($grid[$column][$row + 3] !== \ConnectFour::DISC_EMPTY)) {
                return false;
            }
            for ($i = 0; $i < 3; $i++) {
                if ($grid[$column][$row + $i] === \ConnectFour::DISC_EMPTY) {
                    return false;
                } else if ($grid[$column][$row + $i] == $player) {
                    if ((++$count) == 3) {
                        return $column;
                    }
                } else {
                    return false;
                }
            }
            //echo "boo";
            return false;
        }

        protected function diagonal($column, $row, $player, $direction = 1) {
            // ascending
            $grid = $this->getBoard()->getGrid();
            $width = $this->getBoard()->getPlayer()->getWidth();
            $height = $this->getBoard()->getPlayer()->getHeight();
            //echo __METHOD__ . " col: $column, row: $row, dir $direction\n";


            $empty = 0;
            $col = 0;

            $changed = false;

            if (($column + 3 * $direction) < 0) {
                return false;
            }
            if (($column + 3 * $direction) >= $this->getBoard()->getWidth() ||
                    ($row + 3) >= $this->getBoard()->getHeight()) {
                return false;
            }


            for ($i = 0; $i <= 3; $i++) {
                //echo "I: $i\n";

                $i *= $direction;

                if ($grid[$column + $i][$row + abs($i)] == \ConnectFour::DISC_EMPTY) {
                    $val = $grid[$column + $i][$row + abs($i)];
                    if (++$empty == 2) {
                        return false;
                    }
                    //check if column below is filled

                    $filled = true;
                    $set = $row + abs($i) - 1;

                    if ($set >= 0) {
                        if ($grid[$column + $i][$set] == \ConnectFour::DISC_EMPTY) {
                            $filled = false;
                        }
                    }

                    if ($filled == true) {
                        //echo "cc: $column, i: $i\n";
                        $col = $column + $i;
                        $changed = true;
                    }
                } else if ($grid[$column + $i][$row + abs($i)] != $player) {
                    return false;
                }

                $i = abs($i);
            }

            if (!$changed) {
                return false;
            }
            //echo "return col: $col\n";
            return $col;
        }

    }

}